/*---------------------------------------------------------------------------*\
  =========                 |
  \\      /  F ield         | OpenFOAM: The Open Source CFD Toolbox
   \\    /   O peration     |
    \\  /    A nd           | www.openfoam.com
     \\/     M anipulation  |
-------------------------------------------------------------------------------
    Copyright (C) 2011-2017 OpenFOAM Foundation
    Copyright (C) 2016-2020 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
    This file is part of OpenFOAM.

    OpenFOAM is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
    ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    for more details.

    You should have received a copy of the GNU General Public License
    along with OpenFOAM.  If not, see <http://www.gnu.org/licenses/>.

Application
    blockMesh

Group
    grpMeshGenerationUtilities

Description
    A multi-block mesh generator.

    Uses the block mesh description found in
      - \c system/blockMeshDict
      - \c system/\<region\>/blockMeshDict
      - \c constant/polyMesh/blockMeshDict
      - \c constant/\<region\>/polyMesh/blockMeshDict

Usage
    \b blockMesh [OPTION]

    Options:
      - \par -write-obj
        Write topology as a set of edges in OBJ format and exit.

      - \par -write-vtk
        Write topology as VTK file (xml, ascii) and exit.

      - \par -merge-points
        Merge points instead of default topological merge

      - \par -region \<name\>
        Specify alternative mesh region.

      - \par -dict \<filename\>
        Alternative dictionary for the block mesh description.

      - \par -sets
        Write cellZones as cellSets too (for processing purposes)

      - \par -noClean
        Do not remove any existing polyMesh/ directory or files

      - \par -time
        Write resulting mesh to a time directory (instead of constant)

\*---------------------------------------------------------------------------*/

#include "Time.H"
#include "IOdictionary.H"
#include "IOPtrList.H"

#include "blockMesh.H"
#include "foamVtkInternalMeshWriter.H"
#include "attachPolyTopoChanger.H"
#include "polyTopoChange.H"
#include "emptyPolyPatch.H"
#include "cyclicPolyPatch.H"
#include "cellSet.H"

#include "argList.H"
#include "OSspecific.H"
#include "OFstream.H"

#include "Pair.H"
#include "slidingInterface.H"

using namespace Foam;

// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //

int main(int argc, char *argv[])
{
    argList::addNote
    (
        "Block mesh generator.\n"
        "  The ordering of vertex and face labels within a block as shown "
        "below.\n"
        "  For the local vertex numbering in the sequence 0 to 7:\n"
        "    Faces 0, 1 (x-direction) are left, right.\n"
        "    Faces 2, 3 (y-direction) are front, back.\n"
        "    Faces 4, 5 (z-direction) are bottom, top.\n"
        "\n"
        "                        7 ---- 6\n"
        "                 f5     |\\     |\\     f3\n"
        "                 |      | 4 ---- 5     \\\n"
        "                 |      3 |--- 2 |      \\\n"
        "                 |       \\|     \\|      f2\n"
        "                 f4       0 ---- 1\n"
        "    Y  Z\n"
        "     \\ |                f0 ------ f1\n"
        "      \\|\n"
        "       O--- X\n"
    );

    argList::noParallel();
    argList::noFunctionObjects();

    argList::addBoolOption
    (
        "write-obj",
        "Write block edges and centres as obj files and exit"
    );
    argList::addOptionCompat("write-obj", {"blockTopology", 1912});

    argList::addBoolOption
    (
        "write-vtk",
        "Write topology as VTK file and exit"
    );

    argList::addBoolOption
    (
        "merge-points",
        "Geometric (point) merging instead of topological merging "
        "(slower, fails with high-aspect cells. default for 1912 and earlier)",
        true  // mark as an advanced option
    );
    argList::addBoolOption
    (
        "noClean",
        "Do not remove any existing polyMesh/ directory or files"
    );
    argList::addOption("dict", "file", "Alternative blockMeshDict");
    argList::addBoolOption
    (
        "sets",
        "Write cellZones as cellSets too (for processing purposes)"
    );
    argList::addOption
    (
        "time",
        "time",
        "Specify a time to write mesh to (default: constant)"
    );

    #include "addRegionOption.H"
    #include "setRootCase.H"
    #include "createTime.H"

    // Remove old files, unless disabled
    const bool removeOldFiles = !args.found("noClean");

    // Default merge (topology), unless otherwise specified
    blockMesh::mergeStrategy strategy(blockMesh::DEFAULT_MERGE);

    if (args.found("merge-points"))
    {
        strategy = blockMesh::MERGE_POINTS;
    }

    word regionName(polyMesh::defaultRegion);
    word regionPath;

    // Check if the region is specified otherwise mesh the default region
    if (args.readIfPresent("region", regionName))
    {
        Info<< nl << "Generating mesh for region " << regionName << endl;
        regionPath = regionName;
    }


    // Instance for resulting mesh
    bool useTime = false;
    word meshInstance(runTime.constant());

    if
    (
        args.readIfPresent("time", meshInstance)
     && runTime.constant() != meshInstance
    )
    {
        // Verify that the value is actually good
        scalar timeValue;

        useTime = readScalar(meshInstance, timeValue);
        if (!useTime)
        {
            FatalErrorInFunction
                << "Bad input value: " << meshInstance
                << "Should be a scalar or 'constant'"
                << nl << endl
                << exit(FatalError);
        }
    }


    // Locate appropriate blockMeshDict
    #include "findBlockMeshDict.H"

    blockMesh blocks(meshDict, regionName, strategy);

    if (!blocks.valid())
    {
        // Could/should be Fatal?

        WarningIn(args.executable())
            << "Did not generate any blocks. Stopping." << nl << endl;

        return 1;
    }


    bool quickExit = false;

    if (args.found("write-obj"))
    {
        quickExit = true;

        Info<< nl;

        // Write mesh as edges
        {
            OFstream os(runTime.path()/"blockTopology.obj");

            Info<< "Writing block structure in obj format: "
                << os.name().name() << endl;

            blocks.writeTopology(os);
        }

        // Write centres of blocks
        {
            OFstream os(runTime.path()/"blockCentres.obj");

            Info<< "Writing block centres in obj format: "
                << os.name().name() << endl;

            for (const point& cc : blocks.topology().cellCentres())
            {
                os << "v " << cc.x() << ' ' << cc.y() << ' ' << cc.z() << nl;
            }
        }
    }

    if (args.found("write-vtk"))
    {
        quickExit = true;

        // non-legacy and ASCII (mesh is small, want readable output)
        const vtk::outputOptions writeOpts = vtk::formatType::INLINE_ASCII;

        Info<< nl;

        const polyMesh& topoMesh = blocks.topology();
        const vtk::vtuCells topoCells(topoMesh, writeOpts);

        vtk::internalMeshWriter writer
        (
            topoMesh,
            topoCells,
            writeOpts,
            runTime.path()/"blockTopology"
        );

        Info<< "Writing block topology in vtk format: "
            << args.relativePath(writer.output()).c_str() << endl;

        writer.writeGeometry();
        writer.beginCellData();
        writer.writeCellIDs();
    }


    if (quickExit)
    {
        Info<< "\nEnd\n" << endl;

        return 0;
    }


    // Instance for resulting mesh
    if (useTime)
    {
        Info<< "Writing polyMesh to " << meshInstance << nl << endl;

        // Make sure that the time is seen to be the current time.
        // This is the logic inside regIOobject that resets the instance
        // to the current time before writing
        runTime.setTime(instant(meshInstance), 0);
    }

    if (removeOldFiles)
    {
        const fileName polyMeshPath
        (
            runTime.path()/meshInstance/regionPath/polyMesh::meshSubDir
        );

        if (exists(polyMeshPath))
        {
            if (exists(polyMeshPath/dictName))
            {
                Info<< "Not deleting polyMesh directory "
                    << runTime.relativePath(polyMeshPath) << nl
                    << "    because it contains " << dictName << endl;
            }
            else
            {
                Info<< "Deleting polyMesh directory "
                    << runTime.relativePath(polyMeshPath) << endl;
                rmDir(polyMeshPath);
            }
        }
    }


    Info<< nl << "Creating polyMesh from blockMesh" << endl;

    polyMesh mesh
    (
        IOobject
        (
            regionName,
            meshInstance,
            runTime
        ),
        pointField(blocks.points()),  // Copy, could we re-use space?
        blocks.cells(),
        blocks.patches(),
        blocks.patchNames(),
        blocks.patchDicts(),
        "defaultFaces",               // Default patch name
        emptyPolyPatch::typeName      // Default patch type
    );


    // Handle merging of patch pairs. Dictionary entry "mergePatchPairs"
    #include "mergePatchPairs.H"

    // Set any cellZones
    #include "addCellZones.H"


    // Detect any cyclic patches and force re-ordering of the faces
    {
        bool hasCyclic = false;
        for (const polyPatch& pp : mesh.boundaryMesh())
        {
            if (isA<cyclicPolyPatch>(pp))
            {
                hasCyclic = true;
                break;
            }
        }

        if (hasCyclic)
        {
            Info<< nl << "Detected cyclic patches; ordering boundary faces"
                << endl;
            const word oldInstance = mesh.instance();
            polyTopoChange meshMod(mesh);
            meshMod.changeMesh(mesh, false);
            mesh.setInstance(oldInstance);
        }
    }


    // Set the precision of the points data to 10
    IOstream::defaultPrecision(max(10u, IOstream::defaultPrecision()));

    Info<< nl << "Writing polyMesh with "
        << mesh.cellZones().size() << " cellZones";

    if (args.found("sets") && !mesh.cellZones().empty())
    {
        Info<< " (written as cellSets too)";
    }
    Info<< endl;

    mesh.removeFiles();
    if (!mesh.write())
    {
        FatalErrorInFunction
            << "Failed writing polyMesh."
            << exit(FatalError);
    }

    if (args.found("sets"))
    {
        for (const cellZone& cz : mesh.cellZones())
        {
            cellSet(mesh, cz.name(), cz).write();
        }
    }

    // Write summary
    {
        Info<< "----------------" << nl
            << "Mesh Information" << nl
            << "----------------" << nl
            << "  " << "boundingBox: " << boundBox(mesh.points()) << nl
            << "  " << "nPoints: " << mesh.nPoints() << nl
            << "  " << "nCells: " << mesh.nCells() << nl
            << "  " << "nFaces: " << mesh.nFaces() << nl
            << "  " << "nInternalFaces: " << mesh.nInternalFaces() << nl;

        Info<< "----------------" << nl
            << "Patches" << nl
            << "----------------" << nl;

        for (const polyPatch& p : mesh.boundaryMesh())
        {
            Info<< "  " << "patch " << p.index()
                << " (start: " << p.start()
                << " size: " << p.size()
                << ") name: " << p.name()
                << nl;
        }
    }

    Info<< "\nEnd\n" << endl;

    return 0;
}


// ************************************************************************* //
